home *** CD-ROM | disk | FTP | other *** search
/ Delphi Magazine Collection 2001 / Delphi Magazine Collection 20001 (2001).iso / DISKS / Issue65 / pools / IBDatabasePool.pas < prev    next >
Encoding:
Pascal/Delphi Source File  |  2000-11-28  |  14.6 KB  |  582 lines

  1. unit IBDatabasePool;
  2.  
  3. interface
  4.  
  5. uses
  6.   Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  7.   IBDatabase, IBQuery, DB;
  8.  
  9. {$R IBDatabasePool.dcr}
  10.  
  11. type
  12.   EDatabasePoolMax = class(EDatabaseError);
  13.   TIBDatabasePool = class(TComponent)
  14.   private
  15.     ConList : TThreadList;
  16.     InUseList : TBits;
  17.     FMaxConnections: integer;
  18.     FDatabaseName: String;
  19.     FParams: TStrings;
  20.     FAutoOpen: boolean;
  21.     procedure SetMaxConnections(const Value: integer);
  22.     procedure SetParams(const Value: TStrings);
  23.   protected
  24.   public
  25.     constructor Create(AOwner : TComponent); override;
  26.     destructor Destroy; override;
  27.     procedure OpenAll; virtual;                      // Openall not necessary if AutoOpen = True
  28.     procedure CloseAll; virtual;                     // If you are trying to reopen all connections, you do not need to call CloseAll before calling OpenAll
  29.     function AcquireDB : TIBDatabase; virtual;
  30.     procedure ReleaseDB(IBDB : TIBDatabase); virtual;  // Make sure to call Release for every Aquire!
  31.   published
  32.     property MaxConnections : integer read FMaxConnections write SetMaxConnections;
  33.     property DatabaseName : String read FDatabaseName write FDatabaseName;  // If changing Databasename after connections are made, you must reopen all connections;
  34.     property Params : TStrings read FParams write SetParams;                // ie.. 'USER_NAME=Sysdba'#13'PASSWORD=masterkey'
  35.     Property AutoOpen : boolean read FAutoOpen write FAutoOpen;
  36.   end;
  37.  
  38.   TIBQueryQueue = class;
  39.   EQueryQueueMax = class(EDatabaseError);
  40.  
  41.   TIBQueueItem = class(TObject)
  42.   private
  43.     FExecuteQuery: boolean;
  44.     FReadOnly: boolean;
  45.     FText: string;
  46.     FQueryObject: TIBQuery;
  47.     FIsReady: boolean;
  48.     FNeedFree: boolean;
  49.   public
  50.     constructor Create; virtual;
  51.     property Text : string read FText write FText;
  52.     property ReadOnly : boolean read FReadOnly write FReadOnly;
  53.     property ExecuteQuery : boolean read FExecuteQuery write FExecuteQuery;
  54.     property QueryObject : TIBQuery read FQueryObject write FQueryObject;
  55.     property IsReady : boolean read FIsReady write FIsReady;
  56.     property NeedFree : boolean read FNeedFree write FNeedFree;
  57.   end;
  58.  
  59.   TIBQueueManager = class(TThread)
  60.   private
  61.     IBQueryQueue : TIBQueryQueue;
  62.   protected
  63.     procedure Execute; override;
  64.   public
  65.     constructor Create(QQ : TIBQueryQueue);
  66.   end;
  67.  
  68.   TIBQueryQueue = class(TComponent)
  69.   private
  70.     FIBDatabasePool: TIBDatabasePool;
  71.     Queue : TThreadList;
  72.     QueueManagerList : TTHreadList;
  73.     FMaxQueueFactor: integer;
  74.     FQueueManagers: integer;
  75.     procedure SetIBDatabasePool(const Value: TIBDatabasePool);
  76.     procedure SetQueueManagers(const Value: integer);
  77.   protected
  78.     procedure Notification(AComponent: TComponent; Operation: TOperation); override;
  79.   public
  80.     constructor Create(AOwner : TComponent); override;
  81.     destructor Destroy; override;
  82.     procedure ExecuteSQL(Text : string; Wait : boolean = False); virtual;    // Use for Insert, Update or Delete statements
  83.     function OpenSQL(Text : string; ReadOnly : boolean) : TIBQuery; virtual; // Use for Select statements
  84.     procedure CloseSQL(Query : TIBQuery); virtual;                           // Must call when done with opened query.
  85.   published
  86.     property IBDatabasePool : TIBDatabasePool read FIBDatabasePool write SetIBDatabasePool;
  87.     property MaxQueueFactor : integer read FMaxQueueFactor write FMaxQueueFactor;  // This number multiplied by the number of DB connections in a pool will be the max queued SQL statements  
  88.     property QueueManagers : integer read FQueueManagers write SetQueueManagers;   // Set this to the number of Queue managers you wish to have running simultaneously.  Setting this too high could limit performance.
  89.   end;
  90.  
  91. procedure Register;
  92.  
  93. implementation
  94.  
  95. procedure Register;
  96. begin
  97.   RegisterComponents('Interbase', [TIBDatabasePool]);
  98.   RegisterComponents('Interbase', [TIBQueryQueue]);
  99. end;
  100.  
  101. { TIBDatabasePool }
  102.  
  103. function TIBDatabasePool.AcquireDB: TIBDatabase;
  104. var
  105.   i : integer;
  106. begin
  107.   Result := nil;
  108.   with ConList.LockList do
  109.   try
  110.     i := InUseList.OpenBit;
  111.     if i >= Count then raise EDatabasePoolMax.Create('All database connections are in use. Cannot continue with Aquire.');
  112.     Result := Items[i];
  113.     InUseList[i] := True;
  114.   finally
  115.     ConList.UnlockList;
  116.   end;
  117. end;
  118.  
  119. procedure TIBDatabasePool.CloseAll;
  120. var
  121.   i : integer;
  122. begin
  123.   with ConList.LockList do
  124.   try
  125.     for i := 0 to Count-1 do
  126.     begin
  127.       if TIBDatabase(Items[i]).Connected then
  128.         TIBDatabase(Items[i]).Close;
  129.     end;
  130.   finally
  131.     ConList.UnlockList;
  132.   end;
  133. end;
  134.  
  135. constructor TIBDatabasePool.Create(AOwner: TComponent);
  136. begin
  137.   inherited;
  138.   ConList := TThreadList.Create;
  139.   InUseList := TBits.Create;
  140.   FParams := TStringList.Create;
  141.   FMaxConnections := 0;
  142.   FDatabaseName := '';
  143. end;
  144.  
  145. destructor TIBDatabasePool.Destroy;
  146. begin
  147.   ConList.Free;
  148.   InUseList.Free;
  149.   FParams.Free;
  150.   inherited;
  151. end;
  152.  
  153.  
  154. procedure TIBDatabasePool.OpenAll;
  155. var
  156.   i : integer;
  157. begin
  158.   with ConList.LockList do
  159.   try
  160.     for i := 0 to Count-1 do
  161.     begin
  162.       if TIBDatabase(Items[i]).Connected then
  163.         TIBDatabase(Items[i]).Close;
  164.       TIBDatabase(Items[i]).DatabaseName := FDatabaseName;
  165.       TIBDatabase(Items[i]).Params.Clear;
  166.       TIBDatabase(Items[i]).Params.AddStrings(FParams);
  167.       TIBDatabase(Items[i]).Open;
  168.     end;
  169.   finally
  170.     ConList.UnlockList;
  171.   end;
  172. end;
  173.  
  174. procedure TIBDatabasePool.ReleaseDB(IBDB: TIBDatabase);
  175. begin
  176.   with ConList.LockList do
  177.   try
  178.     InUseList[IndexOf(IBDB)] := False;
  179.   finally
  180.     ConList.UnlockList;
  181.   end;
  182. end;
  183.  
  184. procedure TIBDatabasePool.SetMaxConnections(const Value: integer);
  185. var
  186.   i : integer;
  187.   IBDB : TIBDatabase;
  188. begin
  189.   if Value <=0 then raise EQueryQueueMax.Create('There must be a positive number of Max Connectiosn');
  190.   if csDesigning in ComponentState then
  191.   begin
  192.     FMaxConnections := Value;
  193.     exit;
  194.   end;
  195.   if FMaxConnections < Value then
  196.   begin
  197.     for i := FMaxConnections to Value do
  198.     begin
  199.       IBDB := TIBDatabase.Create(nil);
  200.       IBDB.Params.AddStrings(FParams);
  201.       IBDB.DatabaseName := DatabaseName;
  202.       IBDB.LoginPrompt := False;
  203.       if FAutoOpen then IBDB.Open;
  204.       ConList.Add(IBDB);
  205.     end;
  206.   end else
  207.   begin
  208.     if FMaxConnections > Value then
  209.     begin
  210.       with ConList.LockList do
  211.       try
  212.         while Count < Value do
  213.         begin
  214.           TIBDatabase(Items[Count-1]).Close;
  215.           TIBDatabase(Items[Count-1]).Free;
  216.           Delete(Count-1);
  217.         end;
  218.       finally
  219.         ConList.UnlockList;
  220.       end;
  221.     end;
  222.   end;
  223.  
  224.   FMaxConnections := Value;
  225.   InUseList.Size := Value+1;
  226. end;
  227.  
  228. procedure TIBDatabasePool.SetParams(const Value: TStrings);
  229. begin
  230.   FParams.Assign(Value);
  231. end;
  232.  
  233. { TIBQueryQueue }
  234.  
  235. procedure TIBQueryQueue.CloseSQL(Query: TIBQuery);
  236. var
  237.   IBDB : TIBDatabase;
  238. begin
  239.   IBDB := nil;
  240.   try
  241.     try
  242.       try
  243.         Query.Transaction.Commit;
  244.         Query.Close;
  245.         IBDB := Query.Database;
  246.       except
  247.         Query.Transaction.Rollback;
  248.         raise;
  249.       end;
  250.     finally
  251.       Query.Free;
  252.     end;
  253.   finally;
  254.     IBDatabasePool.ReleaseDB(IBDB);
  255.   end;
  256. end;
  257.  
  258. constructor TIBQueryQueue.Create(AOwner: TComponent);
  259. begin
  260.   inherited;
  261.   Queue := TThreadList.Create;
  262.   QueueManagerList := TThreadList.Create;
  263.   FMaxQueueFactor := 3;
  264.   FQueueManagers := 1;
  265. end;
  266.  
  267. destructor TIBQueryQueue.Destroy;
  268. var
  269.   i : integer;
  270. begin
  271.   with QueueManagerList.LockList do
  272.   try
  273.     for i := 0 to count-1 do
  274.     begin
  275.       TIBQueueManager(Items[i]).Terminate;
  276.       TIBQueueManager(Items[i]).Free;
  277.     end;
  278.   finally
  279.     QueueManagerList.UnLockList;
  280.   end;
  281.   QueueManagerList.Free;
  282.   with Queue.LockList do
  283.   try
  284.     for i := 0 to count-1 do
  285.       TIBQueueItem(Items[i]).Free;
  286.   finally
  287.     Queue.UnlockList;
  288.   end;
  289.   Queue.Free;
  290.   inherited;
  291. end;
  292.  
  293. procedure TIBQueryQueue.ExecuteSQL(Text: string; Wait : boolean = False);
  294. var
  295.   IBDB : TIBDatabase;
  296.   Query : TIBQuery;
  297.   Trans : TIBTransaction;
  298.   o : TIBQueueItem;
  299. begin
  300.   try
  301.     IBDB := IBDatabasePool.AcquireDB;
  302.   except
  303.     with Queue.LockList do
  304.     try
  305.       if Count >= IBDatabasePool.MaxConnections*FMaxQueueFactor then
  306.         raise Exception.Create('The database queue is full.  Please try again in a few seconds.');
  307.     finally
  308.       Queue.UnlockList;
  309.     end;
  310.     o := TIBQueueItem.Create;
  311.     o.Text := Text;
  312.     o.ReadOnly := False;
  313.     o.ExecuteQuery := True;
  314.     Queue.Add(o);
  315.     try
  316.       if Wait then
  317.         while not o.IsReady do
  318.           Application.ProcessMessages
  319.       else
  320.         o.NeedFree := True;
  321.     finally
  322.       o.Free;
  323.     end;
  324.     exit;
  325.   end;
  326.   try
  327.     Query := TIBQuery.Create(nil);
  328.     Trans := TIBTransaction.Create(Query);
  329.     try
  330.       Query.Database := IBDB;
  331.       Query.Transaction := Trans;
  332.       Trans.AddDatabase(IBDB);
  333.       IBDB.AddTransaction(Trans);
  334.  
  335.       Trans.Params.Add('WRITE');
  336.  
  337.       Query.SQL.Text := Text;
  338.  
  339.       Trans.StartTransaction;
  340.       try
  341.         Query.ExecSQL;
  342.         Trans.Commit;
  343.       except
  344.         Trans.Rollback;
  345.         raise;
  346.       end;
  347.     finally
  348.       Query.Free;
  349.     end;
  350.   finally
  351.     IBDatabasePool.ReleaseDB(IBDB);
  352.   end;
  353. end;
  354.  
  355. procedure TIBQueryQueue.Notification(AComponent: TComponent;
  356.   Operation: TOperation);
  357. begin
  358.   inherited;
  359.   if (Operation = opRemove) then
  360.   begin
  361.     if (AComponent = FIBDatabasePool) then
  362.       FIBDatabasePool := nil;
  363.   end;
  364. end;
  365.  
  366.  
  367. function TIBQueryQueue.OpenSQL(Text: string; ReadOnly: boolean): TIBQuery;
  368. var
  369.   IBDB : TIBDatabase;
  370.   Query : TIBQuery;
  371.   Trans : TIBTransaction;
  372.   o : TIBQueueItem;
  373. begin
  374.   Query := TIBQuery.Create(nil);
  375.   Trans := TIBTransaction.Create(Query);
  376.   try
  377.     try
  378.       IBDB := IBDatabasePool.AcquireDB;
  379.     except
  380.       with Queue.LockList do
  381.       try
  382.         if Count >= IBDatabasePool.MaxConnections*FMaxQueueFactor then
  383.           raise Exception.Create('The database queue is full.  Please try again in a few seconds.');
  384.       finally
  385.         Queue.UnlockList;
  386.       end;
  387.       o := TIBQueueItem.Create;
  388.       o.Text := Text;
  389.       o.ReadOnly := ReadOnly;
  390.       o.ExecuteQuery := False;
  391.       o.QueryObject := Query;
  392.       Queue.Add(o);
  393.       try
  394.         while not o.IsReady do
  395.           Application.ProcessMessages;
  396.       finally
  397.         o.Free;
  398.       end;
  399.       Result := Query;
  400.       exit;
  401.     end;
  402.     try
  403.       Query.Database := IBDB;
  404.       Query.Transaction := Trans;
  405.       Trans.AddDatabase(IBDB);
  406.       IBDB.AddTransaction(Trans);
  407.  
  408.       if ReadOnly then
  409.         Trans.Params.Add('READ')
  410.       else
  411.         Trans.Params.Add('WRITE');
  412.  
  413.       Query.SQL.Text := Text;
  414.       Trans.StartTransaction;
  415.       Query.Open;
  416.       Result := Query;
  417.     except
  418.       IBDatabasePool.ReleaseDB(IBDB);
  419.       raise;
  420.     end;
  421.   except
  422.     Query.Free;
  423.     raise;
  424.   end;
  425. end;
  426.  
  427. procedure TIBQueryQueue.SetIBDatabasePool(const Value: TIBDatabasePool);
  428. begin
  429.   FIBDatabasePool := Value;
  430.   if assigned(FIBDatabasePool) then
  431.   begin
  432.     FIBDatabasePool.FreeNotification(self);
  433.   end;
  434. end;
  435.  
  436. procedure TIBQueryQueue.SetQueueManagers(const Value: integer);
  437. var
  438.   i : integer;
  439. begin
  440.   if Value <=0 then raise Exception.Create('There must be a positive number of Queue Managers');
  441.   if csDesigning in ComponentState then
  442.   begin
  443.     FQueueManagers := Value;
  444.     exit;
  445.   end;
  446.   if (FQueueManagers < Value) or (csLoading in ComponentState) then
  447.   begin
  448.     for i := FQueueManagers to Value do
  449.     begin
  450.       QueueManagerList.Add(TIBQueueManager.Create(Self));
  451.     end;
  452.   end else
  453.   begin
  454.     if FQueueManagers > Value then
  455.     begin
  456.       with QueueManagerList.LockList do
  457.       try
  458.         while Count < Value do
  459.         begin
  460.           TIBQueueManager(Items[Count-1]).Terminate;
  461.           Delete(Count-1);
  462.         end;
  463.       finally
  464.         QueueManagerList.UnlockList;
  465.       end;
  466.     end;
  467.   end;
  468. end;
  469.  
  470. { TIBQueueManager }
  471.  
  472. constructor TIBQueueManager.Create(QQ: TIBQueryQueue);
  473. begin
  474.   inherited Create(True);
  475.   IBQueryQueue := QQ;
  476.   Priority := tpNormal;
  477.   Resume;
  478. end;
  479.  
  480. procedure TIBQueueManager.Execute;
  481. var
  482.   iCount : integer;
  483.   IBDB : TIBDatabase;
  484.   Query : TIBQuery;
  485.   Trans : TIBTransaction;
  486.   o : TIBQueueItem;
  487.   OkToRelease : boolean;
  488. begin
  489.   OkToRelease := True;
  490.   IBDB := nil;
  491.   o := nil;
  492.   while not Terminated do
  493.   begin
  494.     Priority := tpNormal;
  495.     if not assigned(IBQueryQueue.IBDatabasePool) then continue;
  496.     with IBQueryQueue.Queue.LockList do
  497.     try
  498.       iCount := Count;
  499.     finally
  500.       IBQueryQueue.Queue.UnlockList;
  501.     end;
  502.     if iCount > 0 then
  503.     begin
  504.       try
  505.         IBDB := IBQueryQueue.IBDatabasePool.AcquireDB;
  506.       except
  507.         continue;
  508.       end;
  509.       Priority := tpHighest;
  510.       try
  511.         with IBQueryQueue.Queue.LockList do
  512.         try
  513.           if Count = 0 then continue;
  514.           o := TIBQueueItem(Items[0]);
  515.           Delete(0);
  516.         finally
  517.           IBQueryQueue.Queue.UnlockList;
  518.         end;
  519.         if not assigned(o) then raise Exception.Create('Error getting queued object');
  520.         try
  521.           if Assigned(o.QueryObject) then
  522.             Query := o.QueryObject
  523.           else
  524.             Query := TIBQuery.Create(nil);
  525.           Trans := TIBTransaction.Create(Query);
  526.           try
  527.             Query.Database := IBDB;
  528.             Query.Transaction := Trans;
  529.             Trans.AddDatabase(IBDB);
  530.             IBDB.AddTransaction(Trans);
  531.  
  532.             if o.ReadOnly then
  533.               Trans.Params.Add('READ')
  534.             else
  535.               Trans.Params.Add('WRITE');
  536.  
  537.             Query.SQL.Text := o.Text;
  538.             if o.ExecuteQuery then
  539.             begin
  540.               Trans.StartTransaction;
  541.               try
  542.                 Query.ExecSQL;
  543.                 Trans.Commit;
  544.               except
  545.                 Trans.Rollback;
  546.                 raise;
  547.               end;
  548.             end else
  549.             begin
  550.               OkToRelease := False;
  551.               Trans.StartTransaction;
  552.               Query.Open;
  553.             end;
  554.             Priority := tpNormal;
  555.           finally
  556.             if not Assigned(o.QueryObject) then
  557.               Query.Free;
  558.           end;
  559.         finally
  560.           o.IsReady := True;
  561.           if o.NeedFree then
  562.             FreeAndNil(o);
  563.         end;
  564.       finally
  565.         if OkToRelease then
  566.           IBQueryQueue.IBDatabasePool.ReleaseDB(IBDB);
  567.       end;
  568.     end;
  569.   end;
  570. end;
  571.  
  572. { TIBQueueItem }
  573.  
  574. constructor TIBQueueItem.Create;
  575. begin
  576.   inherited Create;
  577.   FIsReady := False;
  578.   FNeedFree := False;
  579. end;
  580.  
  581. end.
  582.